using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

public static class QuaternionExtensions
{
    public static Vector3 xyz(this Quaternion quat)
    {
        return new Vector3(quat.x, quat.y, quat.z);
    }

    public static Vector3 Rotate(this Quaternion quat, Vector3 vec)
    {
        Vector3 r2, r3;
        r2 = Vector3.Cross(quat.xyz(), vec);  //mul+mad 
        r2 = quat.w * vec + r2;     //mad 
        r3 = Vector3.Cross(quat.xyz(), r2);   //mul+mad 
        r3 = r3 * 2 + vec;              //mad 
        return r3;
    }
    

    public static Quaternion Scale(this Quaternion quat, float scale)
    {
        return new Quaternion(scale * quat.x, scale * quat.y, scale * quat.z, scale * quat.w);
    }

    public static Quaternion Add(this Quaternion lhs, Quaternion rhs)
    {
        return new Quaternion(lhs.x + rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w);
    }

    public static Quaternion Normalize(this Quaternion quat)
    {
        float scale = new Vector4(quat.x, quat.y, quat.z, quat.w).magnitude;
        scale = 1.0f / scale;

        return new Quaternion(scale * quat.x, scale * quat.y, scale * quat.z, scale * quat.w);
    }

    public static Matrix4x4 ToMatrix4x4(this Quaternion quat, Vector4 tran)
    {
        Vector3 t = (quat.w * (Vector3)tran - tran.w * quat.xyz() + Vector3.Cross(quat.xyz(), (Vector3)tran)) * 2;
        Matrix4x4 mat = quat.ToMatrix4x4();
        mat.SetColumn(3, new Vector4(t.x, t.y, t.z, 1.0f));
        return mat;
    }

    public static Matrix4x4 ToMatrix4x4(this Quaternion quat)
    {
        float qx = quat.x;
        float qy = quat.y;
        float qz = quat.z;
        float qw = quat.w;


        return new Matrix4x4(new Vector4(1.0f - 2.0f * qy * qy - 2.0f * qz * qz, 2.0f * qx * qy - 2.0f * qz * qw, 2.0f * qx * qz + 2.0f * qy * qw, 0.0f),
                                               new Vector4(2.0f * qx * qy + 2.0f * qz * qw, 1.0f - 2.0f * qx * qx - 2.0f * qz * qz, 2.0f * qy * qz - 2.0f * qx * qw, 0.0f),
                                               new Vector4(2.0f * qx * qz - 2.0f * qy * qw, 2.0f * qy * qz + 2.0f * qx * qw, 1.0f - 2.0f * qx * qx - 2.0f * qy * qy, 0.0f),
                                               new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
    }

    public static Quaternion ToQuaternion(Matrix4x4 mat)
    {
        Quaternion quat = Quaternion.identity;
        float tr = mat.m00 + mat.m11 + mat.m22;

        // check the diagonal
        if (tr > 0.0f)
        {
            float fRoot = Mathf.Sqrt(tr + 1.0f);  // 2w
            quat.w = 0.5f * fRoot;
            fRoot = 0.5f / fRoot;  // 1/(4w)
            quat.x = (mat[2, 1] - mat[1, 2]) * fRoot;
            quat.y = (mat[0, 2] - mat[2, 0]) * fRoot;
            quat.z = (mat[1, 0] - mat[0, 1]) * fRoot;
        }
        else
        {
            // |w| <= 1/2
            int[] s_iNext = { 1, 2, 0 };
            int i = 0;
            if (mat.m11 > mat.m00)
                i = 1;
            if (mat.m22 > mat[i, i])
                i = 2;
            int j = s_iNext[i];
            int k = s_iNext[j];

            float fRoot = Mathf.Sqrt(mat[i, i] - mat[j, j] - mat[k, k] + 1.0f);
            if (fRoot < float.Epsilon)
                return Quaternion.identity;

            quat[i] = 0.5f * fRoot;
            fRoot = 0.5f / fRoot;
            quat.w = (mat[k, j] - mat[j, k]) * fRoot;
            quat[j] = (mat[j, i] + mat[i, j]) * fRoot;
            quat[k] = (mat[k, i] + mat[i, k]) * fRoot;
        }

        return Quaternion.Normalize(quat);
    }
}

namespace UltimateGameTools
{
    namespace MeshSimplifier
    {
        public struct RelevanceMat
        {
            public Matrix4x4 m_Mat4x4;
            public float m_fRelevance;
        }

        [Serializable]
        public class RelevanceSphere
        {
            public RelevanceSphere()
            {
                m_v3Scale = Vector3.one;
            }

            public void SetDefault(Transform target, float fRelevance)
            {
                m_bExpanded = true;
                m_v3Position = Vector3.zero;
                m_v3Rotation = Vector3.zero;
                m_v3Scale = Vector3.one;
                m_fRelevance = fRelevance;
            }

            public bool m_bExpanded;
            public Vector3 m_v3Position;
            public Vector3 m_v3Rotation;
            public Vector3 m_v3Scale;
            public float m_fRelevance;
        }

        public class Simplifier : MonoBehaviour
        {
            #region Types

            /////////////////////////////////////////////////////////////////////////////////////////////////
            // Types
            /////////////////////////////////////////////////////////////////////////////////////////////////
            public delegate void ProgressDelegate(string strTitle, string strProgressMessage, float fT);
            public delegate Vector3 VertexToWorldDelegate(Vector3 wPos);

            public VertexToWorldDelegate _wPosFunction;

            public static bool Cancelled
            {
                get;
                set;
            }

            public static int CoroutineFrameMiliseconds
            {
                get
                {
                    return m_nCoroutineFrameMiliseconds;
                }
                set
                {
                    m_nCoroutineFrameMiliseconds = value;
                }
            }

            #endregion // Types

            #region Properties

            public bool CoroutineEnded
            {
                get;
                set;
            }

            public bool UseEdgeLength
            {
                get
                {
                    return m_bUseEdgeLength;
                }
                set
                {
                    m_bUseEdgeLength = value;
                }
            }

            public bool UseCurvature
            {
                get
                {
                    return m_bUseCurvature;
                }
                set
                {
                    m_bUseCurvature = value;
                }
            }

            public bool ProtectTexture
            {
                get
                {
                    return m_bProtectTexture;
                }
                set
                {
                    m_bProtectTexture = value;
                }
            }

            public bool LockBorder
            {
                get
                {
                    return m_bLockBorder;
                }
                set
                {
                    m_bLockBorder = value;
                }
            }

            #endregion // Properties

            #region Public methods

            /////////////////////////////////////////////////////////////////////////////////////////////////
            // Public methods
            /////////////////////////////////////////////////////////////////////////////////////////////////

            public IEnumerator ProgressiveMesh(GameObject gameObject, Mesh sourceMesh, RelevanceMat[] aRelevanceSpheres, string strProgressDisplayObjectName = "", ProgressDelegate progress = null)
            {
                m_meshOriginal = sourceMesh;

                Vector3[] aVerticesWorld = GetWorldVertices(gameObject);

                if (aVerticesWorld == null)
                {
                    CoroutineEnded = true;
                    yield break;
                }

                if (_wPosFunction != null)
                {
                    for (int i = 0; i < aVerticesWorld.Length; i++)
                    {
                        aVerticesWorld[i] = _wPosFunction(aVerticesWorld[i]);
                    }
                }

                m_listVertexMap = new List<int>();
                m_listVertexPermutationBack = new List<int>();
                m_listVertices = new List<Vertex>();
                m_aListTriangles = new TriangleList[m_meshOriginal.subMeshCount];

                if (progress != null)
                {
                    progress("Preprocessing mesh: " + strProgressDisplayObjectName, "Building unique vertex data", 1.0f);

                    if (Simplifier.Cancelled)
                    {
                        CoroutineEnded = true;
                        yield break;
                    }
                }

                m_meshUniqueVertices = new MeshUniqueVertices();
                m_meshUniqueVertices.BuildData(m_meshOriginal, aVerticesWorld);

                m_nOriginalMeshVertexCount = m_meshUniqueVertices.ListVertices.Count;
                m_fOriginalMeshSize = Mathf.Max(m_meshOriginal.bounds.size.x, m_meshOriginal.bounds.size.y, m_meshOriginal.bounds.size.z);

                m_listHeap = new List<Vertex>(m_meshUniqueVertices.ListVertices.Count);

                for (int i = 0; i < m_meshUniqueVertices.ListVertices.Count; i++)
                {
                    m_listVertexMap.Add(-1);
                    m_listVertexPermutationBack.Add(-1);
                }

                Vector2[] av2Mapping = m_meshOriginal.uv;

                AddVertices(m_meshUniqueVertices.ListVertices, m_meshUniqueVertices.ListVerticesWorld, m_meshUniqueVertices.ListBoneWeights);

                for (int nSubMesh = 0; nSubMesh < m_meshOriginal.subMeshCount; nSubMesh++)
                {
                    int[] anIndices = m_meshOriginal.GetTriangles(nSubMesh);
                    m_aListTriangles[nSubMesh] = new TriangleList();
                    AddFaceListSubMesh(nSubMesh, m_meshUniqueVertices.SubmeshesFaceList[nSubMesh].m_listIndices, anIndices, av2Mapping);
                }

                if (Application.isEditor && !Application.isPlaying)
                {
                    IEnumerator enumerator = ComputeAllEdgeCollapseCosts(strProgressDisplayObjectName, gameObject.transform, aRelevanceSpheres, progress);

                    while (enumerator.MoveNext())
                    {
                        if (Simplifier.Cancelled)
                        {
                            CoroutineEnded = true;
                            yield break;
                        }
                    }
                }
                else
                {
                    yield return StartCoroutine(ComputeAllEdgeCollapseCosts(strProgressDisplayObjectName, gameObject.transform, aRelevanceSpheres, progress));
                }

                int nVertices = m_listVertices.Count;

                Stopwatch sw = Stopwatch.StartNew();

                while (m_listVertices.Count > 0)
                {
                    if (progress != null && ((m_listVertices.Count & 0xFF) == 0))
                    {
                        progress("Preprocessing mesh: " + strProgressDisplayObjectName, "Collapsing edges", 1.0f - ((float)m_listVertices.Count / (float)nVertices));

                        if (Cancelled)
                        {
                            CoroutineEnded = true;
                            yield break;
                        }
                    }

                    if (sw.ElapsedMilliseconds > CoroutineFrameMiliseconds && CoroutineFrameMiliseconds > 0)
                    {
                        yield return null;
                        sw = Stopwatch.StartNew();
                    }

                    Vertex mn = MinimumCostEdge();

                    m_listVertexPermutationBack[m_listVertices.Count - 1] = mn.m_nID;
                    m_listVertexMap[mn.m_nID] = mn.m_collapse != null ? mn.m_collapse.m_nID : -1;
                    Collapse(mn, mn.m_collapse, true, gameObject.transform, aRelevanceSpheres);
                }

                UnityEngine.Profiling.Profiler.EndSample();

                m_listHeap.Clear();

                CoroutineEnded = true;
            }

            public IEnumerator ComputeMeshWithVertexCount(GameObject gameObject, Mesh meshOut, int nVertices, string strProgressDisplayObjectName = "", ProgressDelegate progress = null)
            {
                if (GetOriginalMeshUniqueVertexCount() == -1)
                {
                    CoroutineEnded = true;
                    yield break;
                }

                if (nVertices < 3)
                {
                    CoroutineEnded = true;
                    yield break;
                }

                if (nVertices >= GetOriginalMeshUniqueVertexCount())
                {
                    // Original vertex count requested
#if false
                  meshOut.triangles    = new int[0];
                  meshOut.subMeshCount = m_meshOriginal.subMeshCount;

                  meshOut.vertices     = m_meshOriginal.vertices;
                  meshOut.normals      = m_meshOriginal.normals;
                  meshOut.tangents     = m_meshOriginal.tangents;
                  meshOut.uv           = m_meshOriginal.uv;
                  meshOut.uv2          = m_meshOriginal.uv2;
                  meshOut.colors32     = m_meshOriginal.colors32;
                  meshOut.boneWeights  = m_meshOriginal.boneWeights;
                  meshOut.bindposes    = m_meshOriginal.bindposes;

                  meshOut.triangles    = m_meshOriginal.triangles;
                  meshOut.subMeshCount = m_meshOriginal.subMeshCount;

                  for (int nSubMesh = 0; nSubMesh < m_meshOriginal.subMeshCount; nSubMesh++)
                  {
                    meshOut.SetTriangles(m_meshOriginal.GetTriangles(nSubMesh), nSubMesh);
                  }
#else
                    meshOut = UnityEngine.Object.Instantiate<Mesh>(m_meshOriginal);
#endif
                    meshOut.name = gameObject.name + " simplified mesh";

                    CoroutineEnded = true;
                    yield break;
                }

                m_listVertices = new List<Vertex>();
                m_aListTriangles = new TriangleList[m_meshOriginal.subMeshCount];

                List<Vertex> listVertices = new List<Vertex>();

                AddVertices(m_meshUniqueVertices.ListVertices, m_meshUniqueVertices.ListVerticesWorld, m_meshUniqueVertices.ListBoneWeights);

                for (int i = 0; i < m_listVertices.Count; i++)
                {
                    m_listVertices[i].m_collapse = (m_listVertexMap[i] == -1) ? null : m_listVertices[m_listVertexMap[i]];
                    listVertices.Add(m_listVertices[m_listVertexPermutationBack[i]]);
                }

                Vector2[] av2Mapping = m_meshOriginal.uv;

                for (int nSubMesh = 0; nSubMesh < m_meshOriginal.subMeshCount; nSubMesh++)
                {
                    int[] anIndices = m_meshOriginal.GetTriangles(nSubMesh);
                    m_aListTriangles[nSubMesh] = new TriangleList();
                    AddFaceListSubMesh(nSubMesh, m_meshUniqueVertices.SubmeshesFaceList[nSubMesh].m_listIndices, anIndices, av2Mapping);
                }

                int nTotalVertices = listVertices.Count;

                Stopwatch sw = Stopwatch.StartNew();

                while (listVertices.Count > nVertices)
                {
                    if (progress != null)
                    {
                        float fT = 1.0f;
                        if (nTotalVertices != nVertices && ((listVertices.Count & 0xFF) == 0))
                        {
                            fT = 1.0f - ((float)(listVertices.Count - nVertices) / (float)(nTotalVertices - nVertices));
                            progress("Simplifying mesh: " + strProgressDisplayObjectName, "Collapsing edges", fT);

                            if (Cancelled)
                            {
                                CoroutineEnded = true;
                                yield break;
                            }
                        }
                    }

                    Vertex mn = listVertices[listVertices.Count - 1];
                    listVertices.RemoveAt(listVertices.Count - 1);
                    Collapse(mn, mn.m_collapse, false, null, null);

                    if (sw.ElapsedMilliseconds > CoroutineFrameMiliseconds && CoroutineFrameMiliseconds > 0)
                    {
                        yield return null;
                        sw = Stopwatch.StartNew();
                    }
                }

                Vector3[] av3Vertices = new Vector3[m_listVertices.Count];

                for (int i = 0; i < m_listVertices.Count; i++)
                {
                    m_listVertices[i].m_nID = i;  // reassign id's
                    av3Vertices[i] = m_listVertices[i].m_v3Position;
                }

                if (Application.isEditor && !Application.isPlaying)
                {
                    IEnumerator enumerator = ConsolidateMesh(gameObject, m_meshOriginal, meshOut, m_aListTriangles, av3Vertices, strProgressDisplayObjectName, progress);

                    while (enumerator.MoveNext())
                    {
                        if (Simplifier.Cancelled)
                        {
                            CoroutineEnded = true;
                            yield break;
                        }
                    }
                }
                else
                {
                    yield return StartCoroutine(ConsolidateMesh(gameObject, m_meshOriginal, meshOut, m_aListTriangles, av3Vertices, strProgressDisplayObjectName, progress));
                }

                CoroutineEnded = true;
            }

            public IEnumerator ComputeSubMeshWithVertexCount(GameObject gameObject, Mesh meshOut, int nVertices, int subMeshIdx, string strProgressDisplayObjectName = "", ProgressDelegate progress = null)
            {
                if (GetOriginalMeshUniqueVertexCount() == -1)
                {
                    CoroutineEnded = true;
                    yield break;
                }

                if (nVertices < 3)
                {
                    CoroutineEnded = true;
                    yield break;
                }

                if (nVertices >= GetOriginalMeshUniqueVertexCount())
                {
                    // Original vertex count requested
#if true
                  meshOut.triangles    = new int[0];
                  meshOut.subMeshCount = m_meshOriginal.subMeshCount;

                  meshOut.vertices     = m_meshOriginal.vertices;
                  meshOut.normals      = m_meshOriginal.normals;
                  meshOut.tangents     = m_meshOriginal.tangents;
                  meshOut.uv           = m_meshOriginal.uv;
                  meshOut.uv2          = m_meshOriginal.uv2;
                  meshOut.colors32     = m_meshOriginal.colors32;
                  meshOut.boneWeights  = m_meshOriginal.boneWeights;
                  meshOut.bindposes    = m_meshOriginal.bindposes;

                  meshOut.triangles    = m_meshOriginal.triangles;
                  meshOut.subMeshCount = m_meshOriginal.subMeshCount;

                  for (int nSubMesh = 0; nSubMesh < m_meshOriginal.subMeshCount; nSubMesh++)
                  {
                    meshOut.SetTriangles(m_meshOriginal.GetTriangles(nSubMesh), nSubMesh);
                  }
#else
                    meshOut = UnityEngine.Object.Instantiate<Mesh>(m_meshOriginal);
#endif
                    meshOut.name = gameObject.name + " simplified mesh";

                    CoroutineEnded = true;
                    yield break;
                }

                m_listVertices = new List<Vertex>();

                int subMeshCount = Mathf.Min(subMeshIdx, m_meshOriginal.subMeshCount);

                m_aListTriangles = new TriangleList[subMeshCount];

                List<Vertex> listVertices = new List<Vertex>();

                AddVertices(m_meshUniqueVertices.ListVertices, m_meshUniqueVertices.ListVerticesWorld, m_meshUniqueVertices.ListBoneWeights);

                for (int i = 0; i < m_listVertices.Count; i++)
                {
                    m_listVertices[i].m_collapse = (m_listVertexMap[i] == -1) ? null : m_listVertices[m_listVertexMap[i]];
                    listVertices.Add(m_listVertices[m_listVertexPermutationBack[i]]);
                }

                Vector2[] av2Mapping = m_meshOriginal.uv;

                for (int nSubMesh = 0; nSubMesh < m_aListTriangles.Length; nSubMesh++)
                {
                    int[] anIndices = m_meshOriginal.GetTriangles(nSubMesh);
                    m_aListTriangles[nSubMesh] = new TriangleList();
                    AddFaceListSubMesh(nSubMesh, m_meshUniqueVertices.SubmeshesFaceList[nSubMesh].m_listIndices, anIndices, av2Mapping);
                }

                int nTotalVertices = listVertices.Count;

                Stopwatch sw = Stopwatch.StartNew();

                while (listVertices.Count > nVertices)
                {
                    if (progress != null)
                    {
                        float fT = 1.0f;
                        if (nTotalVertices != nVertices && ((listVertices.Count & 0xFF) == 0))
                        {
                            fT = 1.0f - ((float)(listVertices.Count - nVertices) / (float)(nTotalVertices - nVertices));
                            progress("Simplifying mesh: " + strProgressDisplayObjectName, "Collapsing edges", fT);

                            if (Cancelled)
                            {
                                CoroutineEnded = true;
                                yield break;
                            }
                        }
                    }

                    Vertex mn = listVertices[listVertices.Count - 1];
                    listVertices.RemoveAt(listVertices.Count - 1);
                    Collapse(mn, mn.m_collapse, false, null, null);

                    if (sw.ElapsedMilliseconds > CoroutineFrameMiliseconds && CoroutineFrameMiliseconds > 0)
                    {
                        yield return null;
                        sw = Stopwatch.StartNew();
                    }
                }

                Vector3[] av3Vertices = new Vector3[m_listVertices.Count];

                for (int i = 0; i < m_listVertices.Count; i++)
                {
                    m_listVertices[i].m_nID = i;  // reassign id's
                    av3Vertices[i] = m_listVertices[i].m_v3Position;
                }

                if (Application.isEditor && !Application.isPlaying)
                {
                    IEnumerator enumerator = ConsolidateSubMesh(gameObject, m_meshOriginal, meshOut, subMeshIdx, m_aListTriangles, av3Vertices, strProgressDisplayObjectName, progress);

                    while (enumerator.MoveNext())
                    {
                        if (Simplifier.Cancelled)
                        {
                            CoroutineEnded = true;
                            yield break;
                        }
                    }
                }
                else
                {
                    yield return StartCoroutine(ConsolidateSubMesh(gameObject, m_meshOriginal, meshOut, subMeshIdx, m_aListTriangles, av3Vertices, strProgressDisplayObjectName, progress));
                }

                CoroutineEnded = true;
            }

            public int GetOriginalMeshUniqueVertexCount()
            {
                return m_nOriginalMeshVertexCount;
            }

            public int GetOriginalMeshTriangleCount()
            {
                return m_meshOriginal.triangles.Length / 3;
            }

            public static Vector3[] GetWorldVertices(GameObject gameObject)
            {
                Vector3[] aVertices = null;

                SkinnedMeshRenderer skin = gameObject.GetComponent<SkinnedMeshRenderer>();
                MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();

                Transform[] aBones = null;
                Mesh sharedMesh = null;
                if (skin != null && skin.sharedMesh != null)
                {
                    aBones = skin.bones;
                    sharedMesh = skin.sharedMesh;
                }
                else if (meshFilter != null && meshFilter.sharedMesh != null)
                {
                    sharedMesh = meshFilter.sharedMesh;
                }
                else
                    return null;

                aVertices = sharedMesh.vertices;
                BoneWeight[] aBoneWeights = sharedMesh.boneWeights;
                Matrix4x4[] aBindPoses = sharedMesh.bindposes;

                if (aVertices == null /*|| aBoneWeights == null || aBindPoses == null || aBones == null*/)
                {
                    return null;
                }

                if ((aBones == null || aBones.Length == 0) || aBoneWeights.Length == 0 || aBindPoses.Length == 0)
                {
                    for (int nVertex = 0; nVertex < aVertices.Length; nVertex++)
                    {
                        aVertices[nVertex] = gameObject.transform.TransformPoint(aVertices[nVertex]);
                    }
                }
                else
                {
                    for (int nVertex = 0; nVertex < aVertices.Length; nVertex++)
                    {
                        BoneWeight bw = aBoneWeights[nVertex];
                        Vector3 v3World = Vector3.zero;

                        Vector3 v3LocalVertex;
                        if (Math.Abs(bw.weight0) > 0.00001f)
                        {
                            Transform bone = (aBones == null || aBones.Length == 0) ? gameObject.transform : aBones[bw.boneIndex0].transform;
                            v3World = (bone.localToWorldMatrix * aBindPoses[bw.boneIndex0]).MultiplyPoint3x4(aVertices[nVertex]) * bw.weight0;
                        }
                        if (Math.Abs(bw.weight1) > 0.00001f)
                        {
                            Transform bone = (aBones == null || aBones.Length == 0) ? gameObject.transform : aBones[bw.boneIndex1].transform;
                            v3World += (bone.localToWorldMatrix * aBindPoses[bw.boneIndex1]).MultiplyPoint3x4(aVertices[nVertex]) * bw.weight1;
                        }
                        if (Math.Abs(bw.weight2) > 0.00001f)
                        {
                            Transform bone = (aBones == null || aBones.Length == 0) ? gameObject.transform : aBones[bw.boneIndex2].transform;
                            v3World += (bone.localToWorldMatrix * aBindPoses[bw.boneIndex2]).MultiplyPoint3x4(aVertices[nVertex]) * bw.weight2;
                        }
                        if (Math.Abs(bw.weight3) > 0.00001f)
                        {
                            Transform bone = (aBones == null || aBones.Length == 0) ? gameObject.transform : aBones[bw.boneIndex3].transform;
                            v3World += (bone.localToWorldMatrix * aBindPoses[bw.boneIndex3]).MultiplyPoint3x4(aVertices[nVertex]) * bw.weight3;
                        }
                        aVertices[nVertex] = v3World;
                    }
                }

                return aVertices;
            }

#endregion // Public methods

#region Private methods

            /////////////////////////////////////////////////////////////////////////////////////////////////
            // Private methods
            /////////////////////////////////////////////////////////////////////////////////////////////////

            IEnumerator ConsolidateMesh(GameObject gameObject, Mesh meshIn, Mesh meshOut, TriangleList[] aListTriangles, Vector3[] av3Vertices, string strProgressDisplayObjectName = "", ProgressDelegate progress = null)
            {
                Vector3[] av3NormalsIn = meshIn.normals;
                Vector4[] av4TangentsIn = meshIn.tangents;
                Vector2[] av2Mapping1In = meshIn.uv;
                Vector2[] av2Mapping2In = meshIn.uv2;
                Color[] acolColorsIn = meshIn.colors;
                Color32[] aColors32In = meshIn.colors32;

                List<List<int>> listlistIndicesOut = new List<List<int>>();
                List<Vector3> listVerticesOut = new List<Vector3>();
                List<Vector3> listNormalsOut = new List<Vector3>();
                List<Vector4> listTangentsOut = new List<Vector4>();
                List<Vector2> listMapping1Out = new List<Vector2>();
                List<Vector2> listMapping2Out = new List<Vector2>();
                List<Color32> listColors32Out = new List<Color32>();
                List<BoneWeight> listBoneWeightsOut = new List<BoneWeight>();

                Dictionary<VertexDataHash, int> dicVertexDataHash2Index = new Dictionary<VertexDataHash, int>(new VertexDataHashComparer());

                bool bUV1 = av2Mapping1In != null && av2Mapping1In.Length > 0;
                bool bUV2 = av2Mapping2In != null && av2Mapping2In.Length > 0;
                bool bNormal = av3NormalsIn != null && av3NormalsIn.Length > 0;
                bool bTangent = av4TangentsIn != null && av4TangentsIn.Length > 0;

                Stopwatch sw = Stopwatch.StartNew();

                for (int nSubMesh = 0; nSubMesh < aListTriangles.Length; nSubMesh++)
                {
                    List<int> listIndicesOut = new List<int>();

                    string strMesh = aListTriangles.Length > 1 ? ("Consolidating submesh " + (nSubMesh + 1)) : "Consolidating mesh";

                    for (int i = 0; i < aListTriangles[nSubMesh].m_listTriangles.Count; i++)
                    {
                        if (progress != null && ((i & 0xFF) == 0))
                        {
                            float fT = aListTriangles[nSubMesh].m_listTriangles.Count == 1 ? 1.0f : ((float)i / (float)(aListTriangles[nSubMesh].m_listTriangles.Count - 1));

                            progress("Simplifying mesh: " + strProgressDisplayObjectName, strMesh, fT);

                            if (Cancelled)
                            {
                                yield break;
                            }
                        }

                        if (sw.ElapsedMilliseconds > CoroutineFrameMiliseconds && CoroutineFrameMiliseconds > 0)
                        {
                            yield return null;
                            sw = Stopwatch.StartNew();
                        }

                        for (int v = 0; v < 3; v++)
                        {
                            int nMappingIndex = aListTriangles[nSubMesh].m_listTriangles[i].IndicesUV[v];
                            int nVertexIndex = aListTriangles[nSubMesh].m_listTriangles[i].Indices[v];

                            bool bColor = false;

                            Vector3 v3Vertex = aListTriangles[nSubMesh].m_listTriangles[i].Vertices[v].m_v3Position;
                            Vector3 v3Normal = bNormal ? av3NormalsIn[nVertexIndex] : Vector3.zero;
                            Vector4 v4Tangent = bTangent ? av4TangentsIn[nVertexIndex] : Vector4.zero;
                            Vector2 uv1 = bUV1 ? av2Mapping1In[nMappingIndex] : Vector2.zero;
                            Vector2 uv2 = bUV2 ? av2Mapping2In[nVertexIndex] : Vector2.zero;
                            Color32 color32 = new Color32(0, 0, 0, 0);

                            if (acolColorsIn != null && acolColorsIn.Length > 0)
                            {
                                color32 = acolColorsIn[nVertexIndex];
                                bColor = true;
                            }
                            else if (aColors32In != null && aColors32In.Length > 0)
                            {
                                color32 = aColors32In[nVertexIndex];
                                bColor = true;
                            }

                            VertexDataHash vdata = new VertexDataHash(v3Vertex, v3Normal, uv1, uv2, color32);

                            if (dicVertexDataHash2Index.ContainsKey(vdata))
                            {
                                // Already exists -> Index
                                listIndicesOut.Add(dicVertexDataHash2Index[vdata]);
                            }
                            else
                            {
                                // Does not exist -> Create + index

                                dicVertexDataHash2Index.Add(vdata, listVerticesOut.Count);
                                listVerticesOut.Add(vdata.Vertex);

                                if (bNormal) listNormalsOut.Add(v3Normal);
                                if (bUV1) listMapping1Out.Add(uv1);
                                if (bUV2) listMapping2Out.Add(uv2);
                                if (bTangent) listTangentsOut.Add(v4Tangent);
                                if (bColor) listColors32Out.Add(color32);

                                if (aListTriangles[nSubMesh].m_listTriangles[i].Vertices[v].m_bHasBoneWeight)
                                {
                                    listBoneWeightsOut.Add(aListTriangles[nSubMesh].m_listTriangles[i].Vertices[v].m_boneWeight);
                                }

                                listIndicesOut.Add(listVerticesOut.Count - 1);
                            }
                        }
                    }

                    listlistIndicesOut.Add(listIndicesOut);
                }

                meshOut.triangles = new int[0];
                meshOut.vertices = listVerticesOut.ToArray();
                meshOut.normals = listNormalsOut.Count > 0 ? listNormalsOut.ToArray() : null;
                meshOut.tangents = listTangentsOut.Count > 0 ? listTangentsOut.ToArray() : null;
                meshOut.uv = listMapping1Out.Count > 0 ? listMapping1Out.ToArray() : null;
                meshOut.uv2 = listMapping2Out.Count > 0 ? listMapping2Out.ToArray() : null;
                meshOut.colors32 = listColors32Out.Count > 0 ? listColors32Out.ToArray() : null;
                meshOut.boneWeights = listBoneWeightsOut.Count > 0 ? listBoneWeightsOut.ToArray() : null;
                meshOut.bindposes = meshIn.bindposes;
                meshOut.subMeshCount = listlistIndicesOut.Count;

                for (int nSubMesh = 0; nSubMesh < listlistIndicesOut.Count; nSubMesh++)
                {
                    meshOut.SetTriangles(listlistIndicesOut[nSubMesh].ToArray(), nSubMesh);
                }

                meshOut.name = gameObject.name + " simplified mesh";
                ;

                progress("Simplifying mesh: " + strProgressDisplayObjectName, "Mesh consolidation done", 1.0f);
            }

            IEnumerator ConsolidateSubMesh(GameObject gameObject, Mesh meshIn, Mesh meshOut, int subMeshIdx, TriangleList[] aListTriangles, Vector3[] av3Vertices, string strProgressDisplayObjectName = "", ProgressDelegate progress = null)
            {
                Vector3[] av3VerticesIn = meshIn.vertices;
                Vector3[] av3NormalsIn = meshIn.normals;
                Vector4[] av4TangentsIn = meshIn.tangents;
                Vector2[] av2Mapping1In = meshIn.uv;
                Vector2[] av2Mapping2In = meshIn.uv2;
                Color[] acolColorsIn = meshIn.colors;
                Color32[] aColors32In = meshIn.colors32;

                List<List<int>> listlistIndicesOut = new List<List<int>>();
                Dictionary<MeshUniqueVertices.UniqueVertex, int> dicVertexDataHash2Index = new Dictionary<MeshUniqueVertices.UniqueVertex, int>(new VertexHashComparer());
 
                Stopwatch sw = Stopwatch.StartNew();

                //reset sub mesh count;
                for (int v = 0; v < av3VerticesIn.Length; v++)
                {
                    int nVertexIndex = v;
                    Vector3 v3Vertex = av3VerticesIn[nVertexIndex];
                    MeshUniqueVertices.UniqueVertex vdata = new MeshUniqueVertices.UniqueVertex(v3Vertex);
                    if (dicVertexDataHash2Index.ContainsKey(vdata))
                    {
                        // Already exists
                        Debug.LogWarning(nVertexIndex + ":Already vertexData:" + meshIn.name);
                    }
                    else
                    {
                        dicVertexDataHash2Index.Add(vdata, nVertexIndex);
                    }
                }

                for (int nSubMesh = 0; nSubMesh < aListTriangles.Length; nSubMesh++)
                {
                    List<int> listIndicesOut = new List<int>();

                    string strMesh = aListTriangles.Length > 1 ? ("Consolidating submesh " + (nSubMesh + 1)) : "Consolidating mesh";

                    for (int i = 0; i < aListTriangles[nSubMesh].m_listTriangles.Count; i++)
                    {
                        if (progress != null && ((i & 0xFF) == 0))
                        {
                            float fT = aListTriangles[nSubMesh].m_listTriangles.Count == 1 ? 1.0f : ((float)i / (float)(aListTriangles[nSubMesh].m_listTriangles.Count - 1));

                            progress("Simplifying mesh: " + strProgressDisplayObjectName, strMesh, fT);

                            if (Cancelled)
                            {
                                yield break;
                            }
                        }

                        if (sw.ElapsedMilliseconds > CoroutineFrameMiliseconds && CoroutineFrameMiliseconds > 0)
                        {
                            yield return null;
                            sw = Stopwatch.StartNew();
                        }

                        for (int v = 0; v < 3; v++)
                        {
                            Vector3 v3Vertex = aListTriangles[nSubMesh].m_listTriangles[i].Vertices[v].m_v3Position;
                            MeshUniqueVertices.UniqueVertex vdata = new MeshUniqueVertices.UniqueVertex(v3Vertex);
                            if (dicVertexDataHash2Index.ContainsKey(vdata))
                            {
                                // Already exists -> Index
                                listIndicesOut.Add(dicVertexDataHash2Index[vdata]);
                            }
                            else
                            {
                                // Does not exist -> Create + index
                                Debug.LogError("Does not exist vertexData " + meshIn.name);
                            }
                        }
                    }

                    listlistIndicesOut.Add(listIndicesOut);
                }

                meshOut.vertices = av3VerticesIn;
                meshOut.normals = av3NormalsIn;
                meshOut.tangents = av4TangentsIn;
                meshOut.uv = av2Mapping1In;
                meshOut.uv2 = av2Mapping2In;
                meshOut.colors32 = aColors32In;
                meshOut.boneWeights = meshIn.boneWeights;
                meshOut.bindposes = meshIn.bindposes;

#if false
                if (meshIn.boneWeights != null && meshIn.boneWeights.Length == meshIn.vertexCount)
                {
                    Vector4[] tangents = new Vector4[meshIn.vertexCount];
                    BoneWeight[] boneWeights = meshIn.boneWeights;
                    for (int i = 0; i < meshIn.vertexCount; ++i)
                    {
                        BoneWeight boneWeight = boneWeights[i];
                        tangents[i].x = boneWeight.boneIndex0;
                        tangents[i].y = boneWeight.weight0;
                        tangents[i].z = boneWeight.boneIndex1;
                        tangents[i].w = boneWeight.weight1;
                    }
                    meshOut.tangents = tangents;
                }
#endif
                //meshOut.triangles = meshIn.triangles;
                int subMeshCount = meshIn.subMeshCount;

                if(subMeshCount <= subMeshIdx)
                    meshOut.subMeshCount = subMeshIdx + listlistIndicesOut.Count;

                for (int nSubMesh = 0; nSubMesh < subMeshIdx; nSubMesh++)
                {
                    meshOut.SetTriangles(meshIn.GetIndices(nSubMesh), nSubMesh);
                }

                for (int nSubMesh = 0; nSubMesh < listlistIndicesOut.Count; nSubMesh++)
                {
                    meshOut.SetTriangles(listlistIndicesOut[nSubMesh].ToArray(), subMeshIdx + nSubMesh);
                }

#if UNITY_EDITOR
                UnityEditor.EditorUtility.SetDirty(meshOut);
#else
                meshOut.name = gameObject.name + " simplified sub mesh";
#endif

                progress("Simplifying sub mesh: " + strProgressDisplayObjectName, "Mesh consolidation done", 1.0f);
            }

            int MapVertex(int nVertex, int nMax)
            {
                if (nMax <= 0)
                {
                    return 0;
                }

                while (nVertex >= nMax)
                {
                    nVertex = m_listVertexMap[nVertex];
                }

                return nVertex;
            }

            float ComputeEdgeCollapseCost(Vertex u, Vertex v, float fRelevanceBias)
            {
                bool bUseEdgeLength = m_bUseEdgeLength;
                bool bUseCurvature = m_bUseCurvature;
                bool bProtectTexture = m_bProtectTexture;
                bool bLockBorder = m_bLockBorder;

                int i;
                float fEdgeLength = bUseEdgeLength ? (Vector3.Magnitude(v.m_v3Position - u.m_v3Position) / m_fOriginalMeshSize) : 1.0f;
                float fCurvature = 0.001f;

                List<Triangle> sides = new List<Triangle>();

                for (i = 0; i < u.m_listFaces.Count; i++)
                {
                    if (u.m_listFaces[i].HasVertex(v))
                    {
                        sides.Add(u.m_listFaces[i]);
                    }
                }

                if (bUseCurvature)
                {
                    for (i = 0; i < u.m_listFaces.Count; i++)
                    {
                        float fMinCurv = 1.0f;

                        for (int j = 0; j < sides.Count; j++)
                        {
                            float dotprod = Vector3.Dot(u.m_listFaces[i].Normal, sides[j].Normal);
                            fMinCurv = Mathf.Min(fMinCurv, (1.0f - dotprod) / 2.0f);
                        }

                        fCurvature = Mathf.Max(fCurvature, fMinCurv);
                    }
                }

                if (u.IsBorder() && sides.Count > 1)
                {
                    fCurvature = 1.0f;
                }

                if (bProtectTexture)
                {
                    bool bNoMatch = true;

                    for (i = 0; i < u.m_listFaces.Count; i++)
                    {
                        for (int j = 0; j < sides.Count; j++)
                        {
                            if (u.m_listFaces[i].HasUVData == false)
                            {
                                bNoMatch = false;
                                break;
                            }

                            if (u.m_listFaces[i].TexAt(u) == sides[j].TexAt(u))
                            {
                                bNoMatch = false;
                            }
                        }
                    }

                    if (bNoMatch)
                    {
                        fCurvature = 1.0f;
                    }
                }

                if (bLockBorder && u.IsBorder())
                {
                    fCurvature = MAX_VERTEX_COLLAPSE_COST;
                }

                fCurvature += fRelevanceBias;

                return fEdgeLength * fCurvature;
            }

            void ComputeEdgeCostAtVertex(Vertex v, Transform transform, RelevanceMat[] aRelevanceSpheres)
            {
                if (v.m_listNeighbors.Count == 0)
                {
                    v.m_collapse = null;
                    v.m_fObjDist = -0.01f;
                    return;
                }

                v.m_fObjDist = MAX_VERTEX_COLLAPSE_COST;
                v.m_collapse = null;

                float fRelevanceBias = 0.0f;

                if (aRelevanceSpheres != null)
                {
                    for (int nSphere = 0; nSphere < aRelevanceSpheres.Length; nSphere++)
                    {
                        Vector3 v3World = v.m_v3PositionWorld;
                        Vector3 v3Local = aRelevanceSpheres[nSphere].m_Mat4x4.MultiplyPoint(v3World);

                        if (v3Local.magnitude <= 0.5f)
                        {
                            // Inside
                            fRelevanceBias = aRelevanceSpheres[nSphere].m_fRelevance;
                        }
                    }
                }

                for (int i = 0; i < v.m_listNeighbors.Count; i++)
                {
                    float dist = ComputeEdgeCollapseCost(v, v.m_listNeighbors[i], fRelevanceBias);

                    if (v.m_collapse == null || dist < v.m_fObjDist)
                    {
                        v.m_collapse = v.m_listNeighbors[i];
                        v.m_fObjDist = dist;
                    }
                }
            }

            IEnumerator ComputeAllEdgeCollapseCosts(string strProgressDisplayObjectName, Transform transform, RelevanceMat[] aRelevanceSpheres, ProgressDelegate progress = null)
            {
                Stopwatch sw = Stopwatch.StartNew();

                for (int i = 0; i < m_listVertices.Count; i++)
                {
                    if (progress != null && ((i & 0xFF) == 0))
                    {
                        progress("Preprocessing mesh: " + strProgressDisplayObjectName, "Computing edge collapse cost", m_listVertices.Count == 1 ? 1.0f : ((float)i / (m_listVertices.Count - 1.0f)));

                        if (Cancelled)
                        {
                            yield break;
                        }
                    }

                    if (sw.ElapsedMilliseconds > CoroutineFrameMiliseconds && CoroutineFrameMiliseconds > 0)
                    {
                        yield return null;
                        sw = Stopwatch.StartNew();
                    }

                    ComputeEdgeCostAtVertex(m_listVertices[i], transform, aRelevanceSpheres);
                    HeapAdd(m_listVertices[i]);
                }
            }

            void Collapse(Vertex u, Vertex v, bool bRecompute, Transform transform, RelevanceMat[] aRelevanceSpheres)
            {
                if (v == null)
                {
                    u.Destructor(this);
                    return;
                }

                int i;
                List<Vertex> tmp = new List<Vertex>();

                for (i = 0; i < u.m_listNeighbors.Count; i++)
                {
                    tmp.Add(u.m_listNeighbors[i]);
                }

                List<Triangle> sides = new List<Triangle>();

                for (i = 0; i < u.m_listFaces.Count; i++)
                {
                    if (u.m_listFaces[i].HasVertex(v))
                    {
                        sides.Add(u.m_listFaces[i]);
                    }
                }

                // update texture mapping

                for (i = 0; i < u.m_listFaces.Count; i++)
                {
                    int j;

                    if (u.m_listFaces[i].HasVertex(v))
                    {
                        continue;
                    }

                    if (u.m_listFaces[i].HasUVData)
                    {
                        for (j = 0; j < sides.Count; j++)
                        {
                            if (u.m_listFaces[i].TexAt(u) == sides[j].TexAt(u))
                            {
                                u.m_listFaces[i].SetTexAt(u, sides[j].TexAt(v));
                                break; // only change tex coords once!
                            }
                        }
                    }

                    // Added support for color or 2nd uv here:

                    /*

                    for (j = 0; j < sides.Count; j++)
                    {
                      if (u.m_listFaces[i].VertexColorAt(u) == sides[j].VertexColorAt(u))
                      {
                        u.m_listFaces[i].SetVertexColorAt(u, sides[j].VertexColorAt(v));
                        break; // only change tex coords once!
                      }
                    }

                    */
                }

                // Delete triangles on edge uv

                for (i = u.m_listFaces.Count - 1; i >= 0; i--)
                {
                    if (i < u.m_listFaces.Count && i >= 0 && u.m_listFaces[i].HasVertex(v))
                    {
                        u.m_listFaces[i].Destructor(this);
                    }
                }

                // Update remaining triangles to have v instead of u

                for (i = u.m_listFaces.Count - 1; i >= 0; i--)
                {
                    u.m_listFaces[i].ReplaceVertex(u, v);
                }

                u.Destructor(this);

                // Recompute the edge collapse costs for neighboring vertices

                if (bRecompute)
                {
                    for (i = 0; i < tmp.Count; i++)
                    {
                        ComputeEdgeCostAtVertex(tmp[i], transform, aRelevanceSpheres);
                        HeapSortUp(tmp[i].m_nHeapSpot);
                        HeapSortDown(tmp[i].m_nHeapSpot);
                    }
                }
            }

            void AddVertices(List<Vector3> listVertices, List<Vector3> listVerticesWorld, List<MeshUniqueVertices.SerializableBoneWeight> listBoneWeights)
            {
                bool bHasBoneWeights = listBoneWeights != null && listBoneWeights.Count > 0;

                for (int i = 0; i < listVertices.Count; i++)
                {
                    new Vertex(this, listVertices[i], listVerticesWorld[i], bHasBoneWeights, bHasBoneWeights ? listBoneWeights[i].ToBoneWeight() : new BoneWeight(), i);
                }
            }

            void AddFaceListSubMesh(int nSubMesh, List<int> listTriangles, int[] anIndices, Vector2[] v2Mapping)
            {
                bool bUVData = false;

                if (v2Mapping != null)
                {
                    if (v2Mapping.Length > 0)
                    {
                        bUVData = true;
                    }
                }

                for (int i = 0; i < listTriangles.Count / 3; i++)
                {
                    Triangle tri = new Triangle(this, nSubMesh,
                                                m_listVertices[listTriangles[i * 3]], m_listVertices[listTriangles[i * 3 + 1]], m_listVertices[listTriangles[i * 3 + 2]],
                                                bUVData, anIndices[i * 3], anIndices[i * 3 + 1], anIndices[i * 3 + 2]);
                    ShareUV(v2Mapping, tri);
                }
            }

            void ShareUV(Vector2[] aMapping, Triangle t)
            {
                if (t.HasUVData == false)
                {
                    return;
                }

                // It so happens that neighboring faces that share vertices
                // sometimes share uv coordinates at those verts but have
                // their own entries in the tex vert list

                if (aMapping == null || aMapping.Length == 0)
                {
                    return;
                }

                for (int i = 0; i < 3; i++)
                {
                    int nCurrentVert = i;

                    for (int j = 0; j < t.Vertices[nCurrentVert].m_listFaces.Count; j++)
                    {
                        Triangle n = t.Vertices[nCurrentVert].m_listFaces[j];

                        if (t == n)
                        {
                            continue;
                        }

                        int tx1 = t.TexAt(t.Vertices[nCurrentVert]);
                        int tx2 = n.TexAt(t.Vertices[nCurrentVert]);

                        if (tx1 == tx2)
                        {
                            continue;
                        }

                        Vector2 uv1 = aMapping[tx1];
                        Vector2 uv2 = aMapping[tx2];

                        if (uv1 == uv2)
                        {
                            t.SetTexAt(t.Vertices[nCurrentVert], tx2);
                        }
                    }
                }
            }

            Vertex MinimumCostEdge()
            {
                // Find the edge that when collapsed will affect model the least.
                // This funtion actually returns a Vertex, the second vertex
                // of the edge (collapse candidate) is stored in the vertex data.
                Vertex hp = HeapPop();
                return hp;
            }

            float HeapValue(int i)
            {
                if (i < 0 || i >= m_listHeap.Count)
                {
                    return 9999999999999.9f;
                }

                if (m_listHeap[i] == null)
                {
                    return 9999999999999.9f;
                }

                return m_listHeap[i].m_fObjDist;
            }

            void HeapSortUp(int k)
            {
                int k2;

                while (HeapValue(k) < HeapValue((k2 = (k - 1) / 2)))
                {
                    Vertex tmp = m_listHeap[k];
                    m_listHeap[k] = m_listHeap[k2];
                    m_listHeap[k].m_nHeapSpot = k;
                    m_listHeap[k2] = tmp;
                    m_listHeap[k2].m_nHeapSpot = k2;
                    k = k2;
                }
            }

            void HeapSortDown(int k)
            {
                int k2;

                if (k == -1)
                {
                    return;
                }

                while (HeapValue(k) > HeapValue((k2 = (k + 1) * 2)) || HeapValue(k) > HeapValue((k2 - 1)))
                {
                    k2 = (HeapValue(k2) < HeapValue(k2 - 1)) ? k2 : k2 - 1;
                    Vertex tmp = m_listHeap[k];
                    m_listHeap[k] = m_listHeap[k2];
                    m_listHeap[k].m_nHeapSpot = k;
                    m_listHeap[k2] = tmp;
                    if (tmp != null) m_listHeap[k2].m_nHeapSpot = k2;
                    k = k2;
                }
            }

            void HeapAdd(Vertex v)
            {
                int k = m_listHeap.Count;
                m_listHeap.Add(v);
                v.m_nHeapSpot = k;
                HeapSortUp(k);
            }

            Vertex HeapPop()
            {
                Vertex rv = m_listHeap[0];
                rv.m_nHeapSpot = -1;
                m_listHeap[0] = null;
                HeapSortDown(0);
                return rv;
            }

#endregion Private methods

#region Private types

            /////////////////////////////////////////////////////////////////////////////////////////////////
            // Private types
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /// <summary>
            /// Stores vertex and mapping information.
            /// </summary>
            private class Triangle
            {
                public Vertex[] Vertices
                {
                    get
                    {
                        return m_aVertices;
                    }
                }

                public bool HasUVData
                {
                    get
                    {
                        return m_bUVData;
                    }
                }

                public int[] IndicesUV
                {
                    get
                    {
                        return m_aUV;
                    }
                }

                public Vector3 Normal
                {
                    get
                    {
                        return m_v3Normal;
                    }
                }

                public int[] Indices
                {
                    get
                    {
                        return m_aIndices;
                    }
                }

                private Vertex[] m_aVertices;
                private bool m_bUVData;
                private int[] m_aUV;
                private int[] m_aIndices;
                private Vector3 m_v3Normal;
                private int m_nSubMesh;

                public Triangle(Simplifier simplifier, int nSubMesh, Vertex v0, Vertex v1, Vertex v2, bool bUVData, int nIndex1, int nIndex2, int nIndex3)
                {
                    m_aVertices = new Vertex[3];
                    m_aUV = new int[3];
                    m_aIndices = new int[3];

                    m_aVertices[0] = v0;
                    m_aVertices[1] = v1;
                    m_aVertices[2] = v2;

                    m_nSubMesh = nSubMesh;

                    m_bUVData = bUVData;

                    if (m_bUVData)
                    {
                        m_aUV[0] = nIndex1;
                        m_aUV[1] = nIndex2;
                        m_aUV[2] = nIndex3;
                    }

                    m_aIndices[0] = nIndex1;
                    m_aIndices[1] = nIndex2;
                    m_aIndices[2] = nIndex3;

                    ComputeNormal();

                    simplifier.m_aListTriangles[nSubMesh].m_listTriangles.Add(this);

                    for (int i = 0; i < 3; i++)
                    {
                        m_aVertices[i].m_listFaces.Add(this);

                        for (int j = 0; j < 3; j++)
                        {
                            if (i != j)
                            {
                                if (m_aVertices[i].m_listNeighbors.Contains(m_aVertices[j]) == false)
                                {
                                    m_aVertices[i].m_listNeighbors.Add(m_aVertices[j]);
                                }
                            }
                        }
                    }
                }

                public void Destructor(Simplifier simplifier)
                {
                    int i;
                    simplifier.m_aListTriangles[m_nSubMesh].m_listTriangles.Remove(this);

                    for (i = 0; i < 3; i++)
                    {
                        if (m_aVertices[i] != null)
                        {
                            m_aVertices[i].m_listFaces.Remove(this);
                        }
                    }

                    for (i = 0; i < 3; i++)
                    {
                        int i2 = (i + 1) % 3;

                        if (m_aVertices[i] == null || m_aVertices[i2] == null) continue;

                        m_aVertices[i].RemoveIfNonNeighbor(m_aVertices[i2]);
                        m_aVertices[i2].RemoveIfNonNeighbor(m_aVertices[i]);
                    }
                }

                public bool HasVertex(Vertex v)
                {
                    return (v == m_aVertices[0] || v == m_aVertices[1] || v == m_aVertices[2]);
                }

                public void ComputeNormal()
                {
                    Vector3 v0 = m_aVertices[0].m_v3Position;
                    Vector3 v1 = m_aVertices[1].m_v3Position;
                    Vector3 v2 = m_aVertices[2].m_v3Position;

                    m_v3Normal = Vector3.Cross((v1 - v0), (v2 - v1));

                    if (m_v3Normal.magnitude == 0.0f) return;

                    m_v3Normal = m_v3Normal.normalized;
                }

                public int TexAt(Vertex vertex)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        if (m_aVertices[i] == vertex)
                        {
                            return m_aUV[i];
                        }
                    }

                    Debug.LogError("TexAt(): Vertex not found");
                    return 0;
                }

                public int TexAt(int i)
                {
                    return m_aUV[i];
                }

                public void SetTexAt(Vertex vertex, int uv)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        if (m_aVertices[i] == vertex)
                        {
                            m_aUV[i] = uv;
                            return;
                        }
                    }

                    Debug.LogError("SetTexAt(): Vertex not found");
                }

                public void SetTexAt(int i, int uv)
                {
                    m_aUV[i] = uv;
                }

                public void ReplaceVertex(Vertex vold, Vertex vnew)
                {
                    if (vold == m_aVertices[0])
                    {
                        m_aVertices[0] = vnew;
                    }
                    else if (vold == m_aVertices[1])
                    {
                        m_aVertices[1] = vnew;
                    }
                    else
                    {
                        m_aVertices[2] = vnew;
                    }

                    int i;

                    vold.m_listFaces.Remove(this);
                    vnew.m_listFaces.Add(this);

                    for (i = 0; i < 3; i++)
                    {
                        vold.RemoveIfNonNeighbor(m_aVertices[i]);
                        m_aVertices[i].RemoveIfNonNeighbor(vold);
                    }

                    for (i = 0; i < 3; i++)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            if (i != j)
                            {
                                if (m_aVertices[i].m_listNeighbors.Contains(m_aVertices[j]) == false)
                                {
                                    m_aVertices[i].m_listNeighbors.Add(m_aVertices[j]);
                                }
                            }
                        }
                    }

                    ComputeNormal();
                }
            };

            /// <summary>
            /// A list of triangles. We encapsulate this as a class to be able to serialize a List of TriangleLists if we need to.
            /// Unity doesn't serialize a list of lists or an array of lists.
            /// </summary>
            private class TriangleList
            {
                public TriangleList()
                {
                    m_listTriangles = new List<Triangle>();
                }

                public List<Triangle> m_listTriangles;
            }

            /// <summary>
            /// Stores topology information and edge collapsing information.
            /// </summary>
            private class Vertex
            {
                public Vector3 m_v3Position;
                public Vector3 m_v3PositionWorld;
                public bool m_bHasBoneWeight;
                public BoneWeight m_boneWeight;
                public int m_nID;           // Place of vertex in original list
                public List<Vertex> m_listNeighbors; // Adjacent vertices
                public List<Triangle> m_listFaces;     // Adjacent triangles
                public float m_fObjDist;      // Cached cost of collapsing edge
                public Vertex m_collapse;      // Candidate vertex for collapse
                public int m_nHeapSpot;     // Heap spot, for optimization purposes.

                public Vertex(Simplifier simplifier, Vector3 v, Vector3 v3World, bool bHasBoneWeight, BoneWeight boneWeight, int nID)
                {
                    m_v3Position = v;
                    m_v3PositionWorld = v3World;
                    m_bHasBoneWeight = bHasBoneWeight;
                    m_boneWeight = boneWeight;
                    this.m_nID = nID;

                    m_listNeighbors = new List<Vertex>();
                    m_listFaces = new List<Triangle>();

                    simplifier.m_listVertices.Add(this);
                }

                public void Destructor(Simplifier simplifier)
                {
                    while (m_listNeighbors.Count > 0)
                    {
                        m_listNeighbors[0].m_listNeighbors.Remove(this);

                        if (m_listNeighbors.Count > 0)
                        {
                            m_listNeighbors.RemoveAt(0);
                        }
                    }

                    simplifier.m_listVertices.Remove(this);
                }

                public void RemoveIfNonNeighbor(Vertex n)
                {
                    if (!m_listNeighbors.Contains(n))
                    {
                        return;
                    }

                    for (int i = 0; i < m_listFaces.Count; i++)
                    {
                        if (m_listFaces[i].HasVertex(n))
                        {
                            return;
                        }
                    }

                    m_listNeighbors.Remove(n);
                }

                public bool IsBorder()
                {
                    int i, j;

                    for (i = 0; i < m_listNeighbors.Count; i++)
                    {
                        int nCount = 0;

                        for (j = 0; j < m_listFaces.Count; j++)
                        {
                            if (m_listFaces[j].HasVertex(m_listNeighbors[i]))
                            {
                                nCount++;
                            }
                        }

                        if (nCount == 1)
                        {
                            return true;
                        }
                    }

                    return false;
                }
            };

            class VertexHashComparer : IEqualityComparer<MeshUniqueVertices.UniqueVertex>
            {
                public bool Equals(MeshUniqueVertices.UniqueVertex a, MeshUniqueVertices.UniqueVertex b)
                {
                    return a == b;
                }

                public int GetHashCode(MeshUniqueVertices.UniqueVertex vdata)
                {
                    return vdata.GetHashCode();
                }
            }

            class VertexDataHashComparer : IEqualityComparer<VertexDataHash>
            {
                public bool Equals(VertexDataHash a, VertexDataHash b)
                {
                    return ((a.UV1 == b.UV1) && (a.UV2 == b.UV2) && (a.Vertex == b.Vertex) && //(a.Normal == b.Normal) &&
                           (a.Color.r == b.Color.r) && (a.Color.g == b.Color.g) && (a.Color.b == b.Color.b) && (a.Color.a == b.Color.a));
                }

                public int GetHashCode(VertexDataHash vdata)
                {
                    return vdata.GetHashCode();
                }
            }

            /// <summary>
            /// Stores vertex data information. Also allows to compare two different objects of this type to
            /// know when two vertices share or not the same data.
            /// </summary>
            class VertexDataHash
            {
                public Vector3 Vertex
                {
                    get
                    {
                        return _v3Vertex;
                    }
                }

                public Vector3 Normal
                {
                    get
                    {
                        return _v3Normal;
                    }
                }

                public Vector2 UV1
                {
                    get
                    {
                        return _v2Mapping1;
                    }
                }

                public Vector2 UV2
                {
                    get
                    {
                        return _v2Mapping2;
                    }
                }

                public Color32 Color
                {
                    get
                    {
                        return _color;
                    }
                }

                public VertexDataHash(Vector3 v3Vertex, Vector3 v3Normal, Vector2 v2Mapping1, Vector2 v2Mapping2, Color32 color)
                {
                    _v3Vertex = v3Vertex;
                    _v3Normal = v3Normal;
                    _v2Mapping1 = v2Mapping1;
                    _v2Mapping2 = v2Mapping2;
                    _color = color;
                    _uniqueVertex = new MeshUniqueVertices.UniqueVertex(v3Vertex);
                    //_uniqueNormal = new MeshUniqueVertices.UniqueVertex(v3Normal);
                }

                public override bool Equals(object obj)
                {
                    VertexDataHash v = obj as VertexDataHash;

                    return ((v._v2Mapping1 == _v2Mapping1) && (v._v2Mapping2 == _v2Mapping2) && (v._v3Vertex == _v3Vertex) && //&& (v._v3Normal == _v3Normal) &&
                            (v._color.r == _color.r) && (v._color.g == _color.g) && (v._color.b == _color.b) && (v._color.a == _color.a));
                }

                public override int GetHashCode()
                {
                    return _uniqueVertex.GetHashCode();// +_uniqueNormal.GetHashCode();
                }

                // Public static

                public static bool operator ==(VertexDataHash a, VertexDataHash b)
                {
                    return a.Equals(b);
                }

                public static bool operator !=(VertexDataHash a, VertexDataHash b)
                {
                    return !a.Equals(b);
                }

                private Vector3 _v3Vertex;
                private Vector3 _v3Normal;
                private Vector2 _v2Mapping1;
                private Vector2 _v2Mapping2;
                private Color32 _color;
                private MeshUniqueVertices.UniqueVertex _uniqueVertex;
                //private MeshUniqueVertices.UniqueVertex _uniqueNormal;
            }

#endregion // Private types

#region Private vars

            /////////////////////////////////////////////////////////////////////////////////////////////////
            // Private vars
            /////////////////////////////////////////////////////////////////////////////////////////////////

            private static int m_nCoroutineFrameMiliseconds = 0;
            private const float MAX_VERTEX_COLLAPSE_COST = 10000000.0f;

            private List<Vertex> m_listVertices;
            private List<Vertex> m_listHeap;
            private TriangleList[] m_aListTriangles;
            [SerializeField, HideInInspector]
            private int m_nOriginalMeshVertexCount = -1;
            [SerializeField, HideInInspector]
            private float m_fOriginalMeshSize = 1.0f;

            [SerializeField, HideInInspector]
            private List<int> m_listVertexMap;
            [SerializeField, HideInInspector]
            private List<int> m_listVertexPermutationBack;

            [SerializeField, HideInInspector]
            private MeshUniqueVertices m_meshUniqueVertices;

            [SerializeField, HideInInspector]
            private Mesh m_meshOriginal;

            [SerializeField, HideInInspector]
            bool m_bUseEdgeLength = true, m_bUseCurvature = true, m_bProtectTexture = true, m_bLockBorder = true;


#endregion // Private vars
        }
    }
}